﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Threading;

namespace XbdSoft.WPFVersion.Common
{
    public class DownloadChangedEventArgs : EventArgs
    {
        public DownloadChangedEventArgs(long totalLength, long downloadlength, DownloadStatus status, string error = null)
        {
            TotalLength = totalLength;
            DownloadLength = Math.Min(downloadlength, totalLength);
            Status = status;
            Error = error;
        }

        public long TotalLength { get; protected set; }
        public long DownloadLength { get; set; }
        public DownloadStatus Status { get; protected set; }
        public string Error { get; protected set; }
    }

    public class WebClientDownload : IDownload
    {
        #region Fields 
        #endregion

        #region Ctor

        public WebClientDownload(string downloadUrl,string savePath,string savefilename)
        {
            DownloadUrl = downloadUrl;
            CheckDownloadUrl(); 
            FileName = GetFileName(DownloadUrl, savefilename);
            BufferSize = 1024;
            DownloadLength = 0;
            Speed = 0F;
            Status = DownloadStatus.Waiting;

            SaveFilePath = savePath;
            if (!Directory.Exists(SaveFilePath))
            {
                Directory.CreateDirectory(SaveFilePath);
            }
            Tag = 0;
            downloadThread = new Thread(new ThreadStart(DoDownload));

        }
        #endregion

        #region Properties

        public string FileName { get; private set; }
        public string SaveFilePath { get; private set; }
        public string DownloadUrl { get; private set; }

        public bool FtpRedirect { get; protected set; }

        public int BufferSize { get; set; }

        private long totalLength = -1;
        public long TotalLength
        {
            get
            {
                if (totalLength < 0)
                {
                    totalLength = GetSize();
                }
                return totalLength;
            }
        }


        public long DownloadLength { get; set; }
        public DownloadStatus Status { get; protected set; }

        public float Speed { get; protected set; }

        public object Tag { get; set; }

        private Thread downloadThread = null;
        #endregion

        #region IDownload Members

        private long GetSize()
        {
            var webRequest = WebRequest.Create(DownloadUrl);
            if (webRequest is FtpWebRequest)
            {
                (webRequest as FtpWebRequest).Method = WebRequestMethods.Ftp.GetFileSize;
            }
            using (var webResponse = webRequest.GetResponse())
            {
                return webResponse.ContentLength;
            }
        }

        private void CheckDownloadUrl()
        {
            var webRequest = WebRequest.Create(DownloadUrl) as HttpWebRequest; ;
            webRequest.AllowAutoRedirect = false;
            webRequest.AddRange(DownloadLength);
            using (var webResponse = webRequest.GetResponse() as HttpWebResponse)
            {
                if (webResponse.StatusCode == HttpStatusCode.OK)
                {
                }
                else if (webResponse.StatusCode == HttpStatusCode.Found)
                {
                    var url = webResponse.Headers["Location"];
                    DownloadUrl = url;
                }
            }
        }
         
        public void Download()
        {
            try
            {
                downloadThread = new Thread(new ThreadStart(DoDownload));
                downloadThread.Start();
            }
            catch { }            
        }

        public void Close()
        {
            try
            {
                if (downloadThread != null)
                {
                    downloadThread.Abort();
                }
            }
            catch { }
        }
        
        public void DoDownload()
        {
            if (DownloadLength >= TotalLength)
                return;

            var response = default(WebResponse);
            var stream = default(Stream);
            var eventArgs = default(DownloadChangedEventArgs);
            try
            {
                OnDownloadStarted(EventArgs.Empty);
                Status = DownloadStatus.Downloading;
                eventArgs = new DownloadChangedEventArgs(TotalLength, DownloadLength, Status);
                OnDownloadChanged(eventArgs);

                var webRequest = WebRequest.Create(DownloadUrl);
                if (webRequest is FtpWebRequest)
                {
                    (webRequest as FtpWebRequest).ContentOffset = DownloadLength;
                }
                else if (webRequest is HttpWebRequest)
                {
                    (webRequest as HttpWebRequest).AddRange(DownloadLength);
                }
                response = webRequest.GetResponse();

                stream = response.GetResponseStream();

                var downloadFileSaveFullPath = Path.Combine(SaveFilePath, FileName);
                var downloadFileInfoFileFullName = Util.GetDownloadInfoFullName(FileName);


                System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
                using (var streamReader = new BinaryReader(stream))
                using (var writer = new FileStream(downloadFileSaveFullPath, FileMode.OpenOrCreate, FileAccess.Write))
                using (var infoWriter = new StreamWriter(downloadFileInfoFileFullName, false))
                {
                    writer.Seek(DownloadLength, SeekOrigin.Begin);
                    var buffer = new byte[BufferSize];
                    var length = 0;
                    bool hasStop = false;
                    long lastLength = 0;

                    watch.Start();
                    while ((length = streamReader.Read(buffer, 0, BufferSize)) > 0)
                    {
                        if (watch.ElapsedMilliseconds >= 1000)
                        {
                            watch.Stop();
                            lastLength += length;
                            hasStop = true;
                            Speed = lastLength * 1000F / (1024 * watch.ElapsedMilliseconds);
                        }
                        DownloadLength += length;
                        lastLength += length;

                        writer.Write(buffer, 0, length);
                        writer.Flush();
                        // Save the DownloadInformation into .xbdtd
                        SaveInfo(infoWriter);

                        Status = DownloadStatus.Downloading;
                        eventArgs = new DownloadChangedEventArgs(TotalLength, DownloadLength, Status);
                        OnDownloadChanged(eventArgs);
                        if (hasStop)
                        {
                            hasStop = false;
                            lastLength = 0;
                            watch.Reset();
                            watch.Start();
                        }
                    }

                    Status = DownloadStatus.Completed;
                    Speed = 0;
                    eventArgs = new DownloadChangedEventArgs(TotalLength, DownloadLength, Status);
                    OnDownloadChanged(eventArgs);

                }

                OnDownloadCompleted(EventArgs.Empty);
            }
            catch (Exception ex)
            {
                Status = DownloadStatus.Paused;
                Speed = 0;
                eventArgs = new DownloadChangedEventArgs(TotalLength, DownloadLength, Status, ex.Message);
                OnDownloadChanged(eventArgs);
            }
            finally
            {
                if (stream != null)
                    stream.Close();
                if (response != null)
                    response.Close();
            } 
        }

        //public void Download2()
        //{
        //    using (WebClient webClient = new WebClient())
        //    {
        //        webClient.Headers.Add("Range", string.Format("bytes={0}", DownloadLength));
        //        webClient.OpenReadCompleted += new OpenReadCompletedEventHandler(WebClient_OpenReadCompleted);
        //        webClient.OpenReadAsync(new Uri(DownloadUrl));
        //    }
        //}

        //void WebClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
        //{
        //    if (e.Error == null)
        //    {
        //        var downloadFileSaveFullPath = Path.Combine(SaveFilePath, FileName);
        //        var downloadFileInfoFileFullName = Util.GetDownloadInfoFullName(FileName);

        //        OnDownloadStarted(EventArgs.Empty);

        //        Status = DownloadStatus.Downloading;
        //        var eventArgs = new DownloadChangedEventArgs(TotalLength, DownloadLength, Status);
        //        OnDownloadChanged(eventArgs);
        //        try
        //        {
        //            System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
        //            using (var streamReader = new BinaryReader(e.Result))
        //            using (var writer = new FileStream(downloadFileSaveFullPath, FileMode.OpenOrCreate, FileAccess.Write))
        //            using (var infoWriter = new StreamWriter(downloadFileInfoFileFullName,false))
        //            {
        //                writer.Seek(DownloadLength, SeekOrigin.Begin); 
        //                var buffer = new byte[BufferSize];
        //                var length = 0; 
        //                bool hasStop = false;
        //                long lastLength = 0;

        //                watch.Start();
        //                while ((length = streamReader.Read(buffer, 0, BufferSize)) > 0)
        //                {
        //                    if (watch.ElapsedMilliseconds >= 1000)
        //                    {
        //                        watch.Stop();
        //                        lastLength += length;
        //                        hasStop = true;
        //                        Speed = lastLength * 1000F / (1024 * watch.ElapsedMilliseconds);
        //                    }
        //                    DownloadLength += length;
        //                    lastLength += length;

        //                    writer.Write(buffer, 0, length);
        //                    writer.Flush();
        //                    // Save the DownloadInformation into .xbdtd
        //                    SaveInfo(infoWriter);

        //                    eventArgs = new DownloadChangedEventArgs(TotalLength, DownloadLength, Status);
        //                    OnDownloadChanged(eventArgs);
        //                    if (hasStop)
        //                    {
        //                        hasStop = false;
        //                        lastLength = 0;
        //                        watch.Reset();
        //                        watch.Start();
        //                    }
        //                }
                        
        //                Status = DownloadStatus.Completed;
        //                Speed = 0;
        //                eventArgs = new DownloadChangedEventArgs(TotalLength, DownloadLength, Status);
        //                OnDownloadChanged(eventArgs);
        //            }

        //            OnDownloadCompleted(EventArgs.Empty);
        //        }
        //        catch (Exception ex)
        //        {
        //            Status = DownloadStatus.Paused;
        //            Speed = 0;
        //            eventArgs = new DownloadChangedEventArgs(TotalLength, DownloadLength, Status, ex.Message);
        //            OnDownloadChanged(eventArgs);
        //        }
        //    }
        //    else
        //    {
        //        Status = DownloadStatus.Paused;
        //        Speed = 0;
        //        var eventArgs = new DownloadChangedEventArgs(TotalLength, DownloadLength, Status);
        //        OnDownloadChanged(eventArgs);
        //    }
        //}

        private void SaveInfo(StreamWriter writer)
        {
            try
            {
                writer.BaseStream.Seek(0, SeekOrigin.Begin);
                writer.WriteLine(string.Format("{0}", Tag));
                writer.WriteLine(string.Format("{0}", DownloadLength));
                writer.Flush();
            }
            catch { }
        }

        #endregion

        #region Virtual Methods

        protected virtual void OnDownloadChanged(DownloadChangedEventArgs e)
        {
            if (DownloadChanged != null)
            {
                DownloadChanged(this, e);
            }
        }

        protected virtual void OnDownloadStarted(EventArgs e)
        {
            if (DownloadStarted != null)
            {
                DownloadStarted(this,e);
            }
        }

        protected virtual void OnDownloadCompleted(EventArgs e)
        {
            if (DownloadCompleted != null)
            {
                DownloadCompleted(this, e);
            }
        }

        #endregion

        #region Helper Methods
        private string GetFileName(string url,string saveFileName="")
        {
            var fileName = string.Empty;
            var index = url.LastIndexOf("/");
            if (index < 0)
                index = url.LastIndexOf(@"\");
            if (index >= 0 && index<url.Length)
                fileName = url.Substring(index+1);
            else
                fileName = url;

            if (!string.IsNullOrEmpty(saveFileName))
            {
                index = fileName.LastIndexOf(".");
                if (index >= 0 && index < fileName.Length)
                {
                    return saveFileName + fileName.Substring(index);
                }
            }
            return fileName;
        }         
        #endregion

        #region Events
        public event EventHandler<DownloadChangedEventArgs> DownloadChanged;

        public event EventHandler<EventArgs> DownloadStarted;

        public event EventHandler<EventArgs> DownloadCompleted;
        #endregion
    }
}
